/******************************************************************************
 * $Id: module.c,v 1.46 2004/09/22 01:14:22 jimb Exp $
 * @file
 * @author  Jim Brooks http://www.jimbrooks.org
 * @brief   Functions for managing source code modules (ie files).
 ******************************************************************************/

#include "common.h"
#include "module.h"

#include "file.h"
#include "tags.h"
#include "statusbar.h"

/******************************************************************************
 * Returns a buffer that holds a source code module.
 * Inserts (or updates) a module_t struct into a hash.
 * The buffer has a null char appended that is NOT accounted in the module size.
 * To free the buffer, call FreeModuleBuffer() !!!not g_free()!!!
 * @param   pModuleName
 *          Name of module.
 * @param   pModuleSize (OUT)
 *          Size of module (undefined if module could not be loaded).
 *          This does NOT account a null char that was appended to buffer.
 * @param   pModuleLoaded (GLOBAL/OUT)
 *          Updated.
 * @param   pHashModules (GLOBAL/OUT)
 *          Updated.
 * @return  Pointer to memory buffer allocated by ** g_malloc() **
 *          -or- NULL upon failure.
 * @remarks When a module is loaded, its buffer is defined by a hashed module_t struct.
 *          Prior to ever loading any module, the tags processing code has built
 *          a hash table of module_t structs (pHashModule) for every module.
 *          Among other things, a module_t holds the buffer's ptr/size.
 *          ## module_t structs are NEVER FREED!! ##
 *          ModuleBuffer() and FreeModuleBuffer() only modify ptr/size in module_t.
 ******************************************************************************/
char*
ModuleBuffer( gchar* pModuleName,
              guint* pModuleSize /* OUT */ )
{
g_assert( NULL != pModuleSize);
   *pModuleSize = 0;

g_return_val_if_fail( IsStringValid( pModuleName, MAX_FILENAME_LEN ), NULL );

  /*
   * The corresponding module_t struct must be in the hash since
   * the tags processing code hashed a module_t for every module.
   */
   pModuleLoaded = (module_t*) g_hash_table_lookup( pHashModules, pModuleName );
g_return_val_if_fail( pModuleLoaded, NULL );

  /*
   * Is the sought module still in a memory buffer?
   * The buffer might have been freed by FreeModuleBuffer().
   * Check only the buffer ptr (size could be zero if a file was empty).
   */
   if ( pModuleLoaded->pModuleBuffer )
   {
     /*
      * Return pointer/size of buffer.
      */
      *pModuleSize = pModuleLoaded->moduleSize;
      return pModuleLoaded->pModuleBuffer;
   }

  /*
   * Load the module into a buffer
   * Note that LoadFile() will append an UNACCOUNTED null char
   * in case other code depends on a trailing null char.
   *
   * For the hashed module_t struct, only update the buffer ptr/size members,
   * leave the rest unchanged!
   */
   pModuleLoaded->pModuleBuffer = LoadFile( pModuleName, pModuleSize /*OUT*/ );
   pModuleLoaded->moduleSize    = *pModuleSize;
   pModuleLoaded->modified      = FALSE;

   if ( pModuleLoaded->pModuleBuffer == NULL )
   {
     /*
      * Oops, couldn't open the module.
      */
      PrintStatusbarThirds( _("Could not open module '"), pModuleName, _("'.") );
      if ( FileSize( pModuleName ) == 0 )
         Warning( "Module %s is empty. ", pModuleName );
      else
         Warning( "Cannot open module %s ", pModuleName );
      return NULL;
   }

#if 0 //use new instead
  /*
   * Does this appear to be a MSDOG file?
   */
   if ( memchr( pModuleLoaded->pModuleBuffer, CHAR_CR, MIN(MAX_LINE_LEN,*pModuleSize) ) )
   {// 这里没有考虑 OSX 的 CR 结尾格式
      char* pBufferNew;
      int   bufferNewSize;

     /*
      * Create a new buffer minus CR chars, then free old buffer.
      */
      if ( FilterMsdosCr( pModuleLoaded->pModuleBuffer,
                          *pModuleSize,
                          &pBufferNew,
                          &bufferNewSize ) == FUNC_OK )
      {
         g_free( pModuleLoaded->pModuleBuffer );
         pModuleLoaded->pModuleBuffer                = pBufferNew;
         pModuleLoaded->moduleSize    = *pModuleSize = bufferNewSize;
	 /** added by lvjinhua **/
	 pModuleLoaded->lineEnding=ENDING_CRLF;
      }
      /*
       * Continue if FilterMsdosCr() failed.
       */
   }
#endif

/*
 * if file has contents
 */
if ( pModuleLoaded->moduleSize )
{

//   pModuleLoaded->lineEnding=ENDING_LF; // Unix's default
   pModuleLoaded->lineEnding=detect_line_ending(pModuleLoaded->pModuleBuffer);

   // convert to LF if it isn't LF
   if ( pModuleLoaded->lineEnding != ENDING_LF )
   {
	   convert_line_ending_to_lf(pModuleLoaded->pModuleBuffer);
       pModuleLoaded->moduleSize = *pModuleSize = strlen(pModuleLoaded->pModuleBuffer);
   }

   /*
    * 这里应该加入检测， 以检测打开对话框是否指定了一特定的字符集， 如果已经指定， 则不检测
    */
   const gchar* charset = NULL;
   pModuleLoaded->pCharset = detect_charset(pModuleLoaded->pModuleBuffer);
   charset = pModuleLoaded->pCharset;

   if ( pModuleLoaded->pCharset == NULL )
   {
   		charset = get_default_charset( );
   }


	GError *err=NULL;
	gchar * str=NULL;
	gsize bytes_read = 0;
		do {
			if (err) {
				DebugMsg("%s: Cannot convert charset from %s to %s\nFile=%s\toffset=%d",
								__func__,
								charset, 
								"UTF-8",
								pModuleName,
								bytes_read );
				charset = "ISO-8859-1";
				g_error_free(err);
				err = NULL;
			}
			str = g_convert(pModuleLoaded->pModuleBuffer, -1, "UTF-8", charset, &bytes_read, NULL, &err);
		} while (err);

		g_free(pModuleLoaded->pModuleBuffer);
		pModuleLoaded->pModuleBuffer = str;
		pModuleLoaded->moduleSize = strlen(str);
		pModuleLoaded->pCharset = charset;
		
		*pModuleSize = pModuleLoaded->moduleSize;
	

#ifdef HYPERSRC_DEBUG
   g_print("File: %s\t Encoding: %s\t Ending: %s\n",
					pModuleLoaded->pModuleName,
					pModuleLoaded->pCharset,
					(pModuleLoaded->lineEnding==ENDING_LF?"LF":
						pModuleLoaded->lineEnding==ENDING_CR?"CR":"CRLF"));
#endif
   /***************************/
}
else
{
	DebugMsg ("%s: %s size == 0", __func__, pModuleName);
}

#ifdef HYPERSRC_PEDANTIC
  /*
   * Check if a null char exists within the buffer,
   * because code which parses buffers would malfunction.
   * LoadFile() has appended the buffer with a null char.
   */
 {
   char* pNull = strchr( pModuleLoaded->pModuleBuffer, '\0' );
   if ( pNull < (char*)pModuleLoaded->pModuleBuffer + *pModuleSize )
   {
      Warning( "Found a null char in middle of file %s \n", pModuleName );
      return NULL;
   }
 }
#endif /* HYPERSRC_PEDANTIC */

  /*
   * Return a pointer to the buffer that holds the module.
   * Buffer size was returned-by-ref to *pModuleSize.
   */
   return pModuleLoaded->pModuleBuffer;
}

/******************************************************************************
 * Free a buffer that holds a module.
 * Updates (does NOT free) the corresponding hashed module_t struct.
 * @param   pModuleName
 *          Name of module.
 * @param   pModuleLoaded->pModuleBuffer (OUT/GLOBAL)
 *          Nullified to indicate module was freed.
 * @param   pModuleLoaded->moduleSize    (OUT/GLOBAL)
 *          Nullified to indicate module was freed.
 ******************************************************************************/
void
FreeModuleBuffer( gchar* pModuleName )
{
   module_t*	pModule = NULL;

#if 0
  /* The original idea was to keep files in memory but functions such as
   * LexPreprocess() modify file buffers during the parsing phase
   * (requiring file to be reloaded in its unmodified state).
   * Besides, Linux will keep (most) files in its buffer cache.
   */
   if ( conserveMemory ) return;
#endif

  /*
   * Skip if name indicates no module is shown in a text widget.
   */
   if ( IsStringEmpty( pModuleName ) )
      return;
g_return_if_fail( IsStringValid( pModuleName, MAX_FILENAME_LEN ) );

  /*
   * Every module should have a hashed module_t struct.
   */
   pModule = (module_t*) g_hash_table_lookup( pHashModules, pModuleName );
g_return_if_fail( pModule );

  /*
   * Free the buffer.
   * Update the hashed module_t to indicate the buffer was freed.
   * Keep the module_t in the hash and don't change the rest of it!
   */
   if ( pModule->pModuleBuffer ) g_free( pModule->pModuleBuffer );
   pModule->pModuleBuffer = NULL;
   pModule->moduleSize    = 0;
   pModule->modified=FALSE;
   pModule->lineEnding    =LF;
   pModule->pCharset      =NULL;

   return;
}

/*****************************************************************************
 * Insert a module_t struct into a hash table (pHashModules).
 * This is intended for when a module is first encountered (before it was loaded).
 * @param   pModuleName
 *          Filename of module ## CALLER MUST PASS PERMANENT STRING!! ##
 * @param   pHashModules (GLOBAL)
 * @return  Pointer to a module_t struct of the module.
 *          Returns NULL if there were too many modules.
 *****************************************************************************/
module_t*
HashDebutModule( char* pModuleName ) /* ## CALLER MUST PASS PERMANENT STRING!! ## */
{
   module_t*	pModule;

g_return_val_if_fail( IsStringValid( pModuleName, MAX_FILENAME_LEN ), NULL );

  /*
   * Too many modules?
   */
   if ( g_hash_table_size( pHashModules ) >= maxModules )
      return NULL;

  /*
   * Don't re-insert the same module, otherwise g_hash_table_insert()
   * will discard the previously inserted module_t and its state!
   * This check is necessary in case the same src file was passed on the cmd-line.
   */
   pModule = g_hash_table_lookup( pHashModules, pModuleName );

   if ( !pModule )
   {
     /*
      * For this newly-encountered module (referenced by a field in a ctags line),
      * create a module_t struct and insert it into a hash table.
      */
      pModule = (module_t*) g_malloc( sizeof(module_t) );

      pModule->pModuleName		= pModuleName;	/* caller is expected to pass permanent string!! */
      pModule->pModuleBuffer	= NULL;			/* was never loaded */
      pModule->moduleSize		= 0;			/* was never loaded */
      pModule->pGslistFuncDef	= NULL;			/* to assign after all tags are processed */
      pModule->pHashFuncTags	= g_hash_table_new( HashFuncTagsKey, HashFuncTagsCmp );

      g_hash_table_insert( pHashModules,
                           pModuleName, /* key */
                           pModule );   /* value */
   }

   return pModule;
}

/******************************************************************************
 * The "hash function" for the hash table of modules.
 ******************************************************************************/
guint
HashModuleKey( gconstpointer pModuleName )
{
   return g_str_hash( pModuleName );
}

/******************************************************************************
 * The comparison function for the hash table of modules.
 ******************************************************************************/
gint
HashModuleCmp( gconstpointer pModuleName1,
               gconstpointer pModuleName2 )
{
   return g_str_equal( pModuleName1,
                       pModuleName2 );
}

/******************************************************************************
 * Find a module from it's name
 ******************************************************************************/
module_t *
FindModule ( gchar * pModuleName )
{
    if ( NULL == pHashModules )
        return NULL;

    return (module_t*) g_hash_table_lookup ( pHashModules,
                                             pModuleName );
}

/*****************************************************************************
 * Create a sorted glist of module names from the module hash table.
 * @param   pHashModules (GLOBAL)
 * @param   pGlistModuleNames (OUT/GLOBAL)
 *****************************************************************************/
void
CreateModuleNameGlist( void )
{
   g_assert( pHashModules ); /* must be created first */

  /*
   * Erase previous module names glist.
   */
   if ( pGlistModuleNames )
   {
      g_list_free( pGlistModuleNames );

     /*
      * Reset to NULL to cause a new glist to be created.
      */
      pGlistModuleNames = NULL;
   }

  /*
   * The modules in the hash are unsorted, so create a glist of sorted module names.
   */
   g_hash_table_foreach( pHashModules,
                         AddSortedModuleGlist,
                         (gpointer)&pGlistModuleNames /* out */ );

  return;
}

/******************************************************************************
 * From an item in the module hash, add a module name to a sorted glist.
 * @param   pModuleName
 *          (hash key)
 *          Pointer to the module name.
 * @param   pUnused
 *          (hash value)
 * @param   ppGlist (IN/OUT)
 *          Pointer^2 to the glist containing module names.
 ******************************************************************************/
void
AddSortedModuleGlist( gpointer pModuleName,
                      gpointer pUnused,
                      gpointer ppGlist )
{
   *(GList**)ppGlist = g_list_insert_sorted( *(GList**)ppGlist,
                                             pModuleName,
                                             HashStrcmp );
   return;
}
